﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace JwtIssuer.Outside.test
{
    [ApiController]
    public class AuthenticateController : BaseOutsideController
    {
        public IActionResult Token([FromBody]UserModel userinfo)
        {
            var loginsuccess = Login(userinfo);//登录验证
            var tokenString = "";
            if (!loginsuccess) return Unauthorized();//登录失败 

            if (loginsuccess)//登录验证成功
            {
                Dictionary<string, object> payLoad = new Dictionary<string, object>();
                payLoad.Add("sub", "rober");
                payLoad.Add("jti", "09e572c7-62d0-4198-9cce-0915d7493806");
                payLoad.Add("nbf", null);
                payLoad.Add("exp", null);
                payLoad.Add("iss", "roberIssuer");
                payLoad.Add("aud", "roberAudience");
                payLoad.Add("age", 30);

                tokenString = CreateToken(payLoad, 30,"randy");

                var result =Validate(tokenString, (load) => { return true; },"randy");

                //再把token与token相关的存储redis中，并设置过期时间

            }
            return Ok(new
            {
                access_token = tokenString,
                token_type = "Bearer"
            });

        }

        public IActionResult Token1()
        {
            var loginsuccess =true;//登录验证
            var tokenString = "";
            if (!loginsuccess) return Unauthorized();//登录失败 

            if (loginsuccess)//登录验证成功
            {
                Dictionary<string, object> payLoad = new Dictionary<string, object>();
                payLoad.Add("sub", "rober");
                payLoad.Add("jti", "09e572c7-62d0-4198-9cce-0915d7493806");
                payLoad.Add("nbf", null);
                payLoad.Add("exp", null);
                payLoad.Add("iss", "roberIssuer");
                payLoad.Add("aud", "roberAudience");
                payLoad.Add("age", 30);

                tokenString = CreateToken(payLoad, 30, "randy");

                var result = Validate(tokenString, (load) => { return true; }, "randy");

            }
            return Ok(new
            {
                access_token = tokenString,
                token_type = "Bearer"
            });

        }

        public static async Task<string> GetAccesssToken(string currentUserId, int expiresMinutes = 2)
        {
            return await Task.Run(() =>
            {
                //约定私钥，下面三个参数可放到配置文件中              
                var secret = "NGUzNmNlNzQtZThkZC00YjRh";

                //发行者
                var iss = "Andre";

                //接受者
                var aud = "Andre";

                if (string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(iss) || string.IsNullOrEmpty(aud))
                    return "";

                if (string.IsNullOrEmpty(currentUserId))
                    currentUserId = Guid.NewGuid().ToString();

                var now = DateTime.UtcNow;
                var claims = new Claim[]
                {
                    new Claim(JwtRegisteredClaimNames.Sub, currentUserId),
                    new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
                };
                var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret));
                var jwt = new JwtSecurityToken(
                       issuer: iss,
                       audience: aud,
                       claims: claims,
                       notBefore: now,
                       expires: now.Add(TimeSpan.FromMinutes(expiresMinutes)),
                       signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
                 );
                return new JwtSecurityTokenHandler().WriteToken(jwt);  //生成一个新的token
            });
        }

        public bool Login(UserModel userinfo)
        {
            if (userinfo.username == "randy" && userinfo.password == "123456")
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// 创建jwttoken,源码自定义
        /// </summary>
        /// <param name="payLoad"></param>
        /// <param name="header"></param>
        /// <returns></returns>
        public static string CreateToken(Dictionary<string, object> payLoad, int expiresMinute, string secretKey, Dictionary<string, object> header = null)
        {
            if (header == null)
            {
                header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {
                    new KeyValuePair<string, object>("alg", "HS256"),
                    new KeyValuePair<string, object>("typ", "JWT")
                });
            }
            //添加jwt可用时间（应该必须要的）
            var authTime = DateTime.UtcNow;
            payLoad["nbf"] = ToUnixEpochDate(authTime);//可用时间起始
            payLoad["exp"] = ToUnixEpochDate(authTime.Add(TimeSpan.FromMinutes(expiresMinute)));//可用时间结束

            var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header));//header base64编码
            var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad));//playload base64编码

            //HMACSHA256 初始化(带密钥)
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey));

            //计算签名
            var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

            //生成jwt
            var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);

            return encodedJwt;
        }

        /// <summary>
        /// 获取jwt中的payLoad
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetPayLoad(string encodeJwt)
        {
            var jwtArr = encodeJwt.Split('.');
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
            return payLoad;
        }

        public static long ToUnixEpochDate(DateTime date) =>
        (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);

        #region +创建jwtToken,采用微软内部方法，默认使用HS256加密，如果需要其他加密方式，请更改源码


        /// <summary>
        /// 创建jwtToken,采用微软内部方法，默认使用HS256加密，如果需要其他加密方式，请更改源码
        /// 返回的结果和CreateToken一样
        /// </summary>
        /// <param name="payLoad"></param>
        /// <param name="expiresMinute">有效分钟</param>
        /// <returns></returns>
        public static string CreateTokenByHandler(Dictionary<string, object> payLoad, int expiresMinute, string secretKey)
        {

            var now = DateTime.UtcNow;

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            // You can add other claims here, if you want:
            var claims = new List<Claim>();
            foreach (var key in payLoad.Keys)
            {
                var tempClaim = new Claim(key, payLoad[key]?.ToString());
                claims.Add(tempClaim);
            }


            // Create the JWT and write it to a string
            var jwt = new JwtSecurityToken(
                issuer: null,
                audience: null,
                claims: claims,
                notBefore: now,
                expires: now.Add(TimeSpan.FromMinutes(expiresMinute)),
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)), SecurityAlgorithms.HmacSha256));
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            return encodedJwt;
        }

        #endregion

        #region 验证jwt

        /// <summary>
        /// 验证身份 验证签名的有效性,
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        /// 例如：payLoad["aud"]?.ToString() == "roberAuddience";
        /// 例如：验证是否过期 等
        /// <returns></returns>
        public static bool Validate(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad, string secretKey)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));

            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey));

            //首先验证签名是否正确（必须的）
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));

            if (!success)
            {
                return success;//签名不正确直接返回
            }

            //其次验证是否在有效期内（也应该必须）
            var now = ToUnixEpochDate(DateTime.UtcNow);
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

            //再其次 进行自定义的验证——自定义的(各复杂的,原理就是获取payLoad 的某个值，然后对这个值进行各种判读--等于，大于，包含，)该jwt是不是进入黑名单,aud==‘roberAudience’
            success = success && validatePayLoad(payLoad);

            return success;
        }

        #endregion

        #region +TW 生成jwt与验证


        /// <summary>
        /// SecurityTokenDescriptor 用來定義 JWT 的相關設定，Subject 屬性則是放置用戶的 Claim 
        ///JwtSecurityTokenHandler 用來產生 JWT、驗證 JWT
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="secretKey"></param>
        /// <param name="expireMinutes"></param>
        /// <returns></returns>
        public static string GenerateToken(string userName, string secretKey, int expireMinutes = 20)
        {
            var symmetricKey = Convert.FromBase64String(secretKey);
            var tokenHandler = new JwtSecurityTokenHandler();

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.Name, userName)
                 }),
                NotBefore = DateTime.UtcNow,
                Expires = DateTime.UtcNow.AddMinutes(Convert.ToInt32(expireMinutes)),

                SigningCredentials =
                    new SigningCredentials(new SymmetricSecurityKey(symmetricKey),
                                           SecurityAlgorithms.HmacSha256Signature)
            };

            var securityToken = tokenHandler.CreateToken(tokenDescriptor);
            var token = tokenHandler.WriteToken(securityToken);
            
            return token;
        }

        /// <summary>
        /// 验证jwt
        /// </summary>
        /// <param name="token"></param>
        /// <param name="secretKey"></param>
        /// <param name="principal"></param>
        /// <returns></returns>
        public static bool TryValidateToken(string token, string secretKey, out ClaimsPrincipal principal)
        {
            principal = null;
            if (string.IsNullOrWhiteSpace(token))
            {
                return false;
            }

            var handler = new JwtSecurityTokenHandler();

            try
            {
                var jwt = handler.ReadJwtToken(token);

                if (jwt == null)
                {
                    return false;
                }

                var secretBytes = Convert.FromBase64String(secretKey);

                var validationParameters = new TokenValidationParameters
                {
                    RequireExpirationTime = true,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    IssuerSigningKey = new SymmetricSecurityKey(secretBytes),

                    //LifetimeValidator = LifetimeValidator

                    ClockSkew = TimeSpan.Zero
                };

                SecurityToken securityToken;
                principal = handler.ValidateToken(token, validationParameters, out securityToken);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion
    }


    public class UserModel
    {
        public string username { get; set; }
        public string password { get; set; }
    }


}